/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2002 Don Schwartz <schwardo@users.sourceforge.net>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2006 Kresten Krab Thorup <krab@gnu.org>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.asupport {


import asruby.Ruby;
import asruby.lang.RubyBoolean;
import asruby.lang.RubyFixnum;
import asruby.lang.RubyFloat;
import asruby.lang.RubyNumeric;
import asruby.lang.RubyObject;
import asruby.lang.RubyProc;
import asruby.lang.RubyString;
import asruby.runtime.Block;
import asruby.runtime.IRubyObject;
import asruby.runtime.MethodIndex;
import asruby.runtime.ThreadContext;

/**
 *
 * @author Jan Arne Petersen, Alan Moore
 */
public class AsUtil {

    public static function convertRubyToJava(rubyObject:IRubyObject):Object{
        //return convertRubyToJava(rubyObject, null);
        return rubyObject;
    }
/*  
    public var RubyConverter:Function = 
        function  convert(context:ThreadContext, rubyObject:IRubyObject):Object {
        };
    
    public static var RUBY_BOOLEAN_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            return Boolean.valueOf(rubyObject.isTrue());
        }
    };
    
    public static var RUBY_BYTE_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_i")) {
                return new Byte((byte) ((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_I, "to_i")).getLongValue());
            }
            return new Byte((byte) 0);
        }
    };
    
    public static var RUBY_SHORT_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_i")) {
                return new Short((short) ((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_I, "to_i")).getLongValue());
            }
            return new Short((short) 0);
        }
    };
    
    public static var RUBY_INTEGER_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_i")) {
                return new Integer((int) ((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_I, "to_i")).getLongValue());
            }
            return new Integer(0);
        }
    };
    
    public static var RUBY_LONG_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_i")) {
                return new Long(((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_I, "to_i")).getLongValue());
            }
            return new Long(0);
        }
    };
    
    public static var RUBY_FLOAT_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_f")) {
                return new Float((float) ((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_F, "to_f")).getDoubleValue());
            }
            return new Float(0.0);
        }
    };
    
    public static var RUBY_DOUBLE_CONVERTER: RubyConverter =  new RubyConverter() {
        public  function convert(context:ThreadContext, rubyObject:IRubyObject):Object{
            if (rubyObject.respondsTo("to_f")) {
                return new Double(((RubyNumeric) rubyObject.callMethod(
                        context, MethodIndex.TO_F, "to_f")).getDoubleValue());
            }
            return new Double(0.0);
        }
    };
    
    public static var RUBY_CONVERTERS: Dictionary =  new HashMap();
    
    static {
        RUBY_CONVERTERS.put(Boolean, RUBY_BOOLEAN_CONVERTER);
        RUBY_CONVERTERS.put(Boolean.TYPE, RUBY_BOOLEAN_CONVERTER);
        RUBY_CONVERTERS.put(Byte, RUBY_BYTE_CONVERTER);
        RUBY_CONVERTERS.put(Byte.TYPE, RUBY_BYTE_CONVERTER);
        RUBY_CONVERTERS.put(Short, RUBY_SHORT_CONVERTER);
        RUBY_CONVERTERS.put(Short.TYPE, RUBY_SHORT_CONVERTER);
        RUBY_CONVERTERS.put(Integer, RUBY_INTEGER_CONVERTER);
        RUBY_CONVERTERS.put(Integer.TYPE, RUBY_INTEGER_CONVERTER);
        RUBY_CONVERTERS.put(Long, RUBY_LONG_CONVERTER);
        RUBY_CONVERTERS.put(Long.TYPE, RUBY_LONG_CONVERTER);
        RUBY_CONVERTERS.put(Float, RUBY_FLOAT_CONVERTER);
        RUBY_CONVERTERS.put(Float.TYPE, RUBY_FLOAT_CONVERTER);
        RUBY_CONVERTERS.put(Double, RUBY_DOUBLE_CONVERTER);
        RUBY_CONVERTERS.put(Double.TYPE, RUBY_DOUBLE_CONVERTER);
    }

    public static function convertRubyToJava(rubyObject:IRubyObject, javaClass:Class):Object{
        if (rubyObject == null || rubyObject.isNil()) {
            return null;
        }
        
        var context: ThreadContext =  rubyObject.getRuntime().getCurrentContext();
        
        if (rubyObject.respondsTo("java_object")) {
        	rubyObject = rubyObject.callMethod(context, "java_object");
        }

        if (rubyObject.respondsTo("to_java_object")) {
        	rubyObject = rubyObject.callMethod(context, "to_java_object");
        }

        if (rubyObject instanceof JavaObject) {
            var value: Object =   ((JavaObject) rubyObject).getValue();
            
            return convertArgument(rubyObject.getRuntime(), value, javaClass);
            
        } else if (javaClass == Object || javaClass == null) {
            //The Java method doesn't care what class it is, but we need to
            //   know what to convert it to, so we use the object's own class.
            //   If that doesn't help, we use String to force a call to the
            //  object's "to_s" method. 
            javaClass = rubyObject.getJavaClass();
        }

        if (javaClass.isInstance(rubyObject)) {
            // rubyObject is already of the required jruby class (or subclass)
            return rubyObject;
        }

        if (javaClass.isPrimitive()) {
            var converter: RubyConverter =  RUBY_CONVERTERS.get(javaClass);
            if (converter != null) {
                return converter.convert(context, rubyObject);
            }

            // XXX this probably isn't good enough -AM
            var s: String =  ((RubyString) rubyObject.callMethod(context, MethodIndex.TO_S, "to_s")).toString();
            if (s.length() > 0) {
                return new Character(s.charAt(0));
            }
			return new Character('\0');
        } else if (javaClass == String) {
            var rubyString: RubyString =  (RubyString) rubyObject.callMethod(context, MethodIndex.TO_S, "to_s");
            var bytes: ByteList =  rubyString.getByteList();
            try {
                return new String(bytes.unsafeBytes(), bytes.begin(), bytes.length(), "UTF8");
            } catch (uee:UnsupportedEncodingException) {
                return new String(bytes.unsafeBytes(), bytes.begin(), bytes.length());
            }
        } else if (javaClass == ByteList) {
            return rubyObject.convertToString().getByteList();
        } else if (javaClass == BigInteger) {
         	if (rubyObject instanceof RubyBignum) {
         		return ((RubyBignum)rubyObject).getValue();
         	} else if (rubyObject instanceof RubyNumeric) {
 				return  BigInteger.valueOf (((RubyNumeric)rubyObject).getLongValue());
         	} else if (rubyObject.respondsTo("to_i")) {
         		var rubyNumeric: RubyNumeric =  ((RubyNumeric)rubyObject.callMethod(context,MethodIndex.TO_F, "to_f"));
 				return  BigInteger.valueOf (rubyNumeric.getLongValue());
         	}
        } else if (javaClass == BigDecimal && !(rubyObject instanceof JavaObject)) {
         	if (rubyObject.respondsTo("to_f")) {
             	var double_value: double =  ((RubyNumeric)rubyObject.callMethod(context,MethodIndex.TO_F, "to_f")).getDoubleValue();
             	return new BigDecimal(double_value);
         	}
        }
        try {
            return ((JavaObject) rubyObject).getValue();
        } catch (ex:ClassCastException) {
            ex.printStackTrace();
            return null;
        }
    }

    public static function convertJavaArrayToRuby(runtime:Ruby, objects:Array):Array{
        var rubyObjects: Array =  new IRubyObject[objects.length];
        for ( var int:i = 0; i < objects.length; i++) {
            rubyObjects[i] = convertJavaToRuby(runtime, objects[i]);
        }
        return rubyObjects;
    }
    
    public interface JavaConverter {
        public IRubyObject convert(Ruby runtime, Object object);
    }
    
    public static var JAVA_DEFAULT_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) {
                return runtime.getNil();
            }

            if (object instanceof IRubyObject) {
                return (IRubyObject) object;
            }
 
            // Note: returns JavaObject instance, which is not
            // directly usable. probably too late to change this now,
            // supplying alternate method convertJavaToUsableRubyObject
            return JavaObject.wrap(runtime, object);
        }
    };
    
    public static var JAVA_BOOLEAN_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyBoolean.newBoolean(runtime, ((Boolean)object).booleanValue());
        }
    };
    
    public static var JAVA_FLOAT_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFloat.newFloat(runtime, ((Float)object).doubleValue());
        }
    };
    
    public static var JAVA_DOUBLE_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFloat.newFloat(runtime, ((Double)object).doubleValue());
        }
    };
    
    public static var JAVA_CHAR_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFixnum.newFixnum(runtime, ((Character)object).charValue());
        }
    };
    
    public static var JAVA_BYTE_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFixnum.newFixnum(runtime, ((Byte)object).byteValue());
        }
    };
    
    public static var JAVA_SHORT_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFixnum.newFixnum(runtime, ((Short)object).shortValue());
        }
    };
    
    public static var JAVA_INT_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFixnum.newFixnum(runtime, ((Integer)object).intValue());
        }
    };
    
    public static var JAVA_LONG_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyFixnum.newFixnum(runtime, ((Long)object).longValue());
        }
    };
    
    public static var JAVA_STRING_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyString.newUnicodeString(runtime, (String)object);
        }
    };
    
    public static var BYTELIST_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyString.newString(runtime, (ByteList)object);
        }
    };
    
    public static var JAVA_BIGINTEGER_CONVERTER: JavaConverter =  new JavaConverter() {
        public  function convert(runtime:Ruby, object:Object):IRubyObject{
            if (object == null) return runtime.getNil();
            return RubyBignum.newBignum(runtime, (BigInteger)object);
        }
    };
    
    private static var JAVA_CONVERTERS: Dictionary = 
        new HashMap();
    
    static {
        JAVA_CONVERTERS.put(Byte, JAVA_BYTE_CONVERTER);
        JAVA_CONVERTERS.put(Byte.TYPE, JAVA_BYTE_CONVERTER);
        JAVA_CONVERTERS.put(Short, JAVA_SHORT_CONVERTER);
        JAVA_CONVERTERS.put(Short.TYPE, JAVA_SHORT_CONVERTER);
        JAVA_CONVERTERS.put(Character, JAVA_CHAR_CONVERTER);
        JAVA_CONVERTERS.put(Character.TYPE, JAVA_CHAR_CONVERTER);
        JAVA_CONVERTERS.put(Integer, JAVA_INT_CONVERTER);
        JAVA_CONVERTERS.put(Integer.TYPE, JAVA_INT_CONVERTER);
        JAVA_CONVERTERS.put(Long, JAVA_LONG_CONVERTER);
        JAVA_CONVERTERS.put(Long.TYPE, JAVA_LONG_CONVERTER);
        JAVA_CONVERTERS.put(Float, JAVA_FLOAT_CONVERTER);
        JAVA_CONVERTERS.put(Float.TYPE, JAVA_FLOAT_CONVERTER);
        JAVA_CONVERTERS.put(Double, JAVA_DOUBLE_CONVERTER);
        JAVA_CONVERTERS.put(Double.TYPE, JAVA_DOUBLE_CONVERTER);
        JAVA_CONVERTERS.put(Boolean, JAVA_BOOLEAN_CONVERTER);
        JAVA_CONVERTERS.put(Boolean.TYPE, JAVA_BOOLEAN_CONVERTER);
        
        JAVA_CONVERTERS.put(String, JAVA_STRING_CONVERTER);
        
        JAVA_CONVERTERS.put(ByteList, BYTELIST_CONVERTER);
        
        JAVA_CONVERTERS.put(BigInteger, JAVA_BIGINTEGER_CONVERTER);

    }
    
    public static function getJavaConverter(clazz:Class):JavaConverter{
        var converter: JavaConverter =  JAVA_CONVERTERS.get(clazz);
        
        if (converter == null) {
            converter = JAVA_DEFAULT_CONVERTER;
        }
        
        return converter;
    }
*/
    /**
     * Converts object to the corresponding Ruby type; however, for non-primitives,
     * a JavaObject instance is returned. This must be subsequently wrapped by
     * calling one of Java.wrap, Java.java_to_ruby, Java.new_instance_for, or
     * Java.getInstance, depending on context.
     * 
     * @param runtime
     * @param object 
     * @return corresponding Ruby type, or a JavaObject instance
     */
    public static function convertJavaToRuby(runtime:Ruby, object:Object):IRubyObject{
        if (object == null) {
            return runtime.getNil();
        }
        return convertJavaToRuby2(runtime, object, object.getClass());
    }

    public static function convertJavaToRuby2(runtime:Ruby, object:Object, javaClass:Class):IRubyObject{
        //return getJavaConverter(javaClass).convert(runtime, object);
        return null;
    }
    
    /**
     * Returns a usable RubyObject; for types that are not converted to Ruby native
     * types, a Java proxy will be returned. 
     * 
     * @param runtime
     * @param object
     * @return corresponding Ruby type, or a functional Java proxy
     *
    public static function convertJavaToUsableRubyObject(runtime:Ruby, object:Object):IRubyObject{
        if (object == null) return runtime.getNil();
        var converter: JavaConverter =  JAVA_CONVERTERS.get(object.getClass());
        if (converter == null || converter == JAVA_DEFAULT_CONVERTER) {
            return Java.getInstance(runtime, object);
        }
        return converter.convert(runtime, object);
    }

    public static function primitiveToWrapper(type:Class):Class{
        if (type == Double.TYPE) {
            return Double;
        } else if (type == Float.TYPE) {
            return Float;
        } else if (type == Integer.TYPE) {
            return Integer;
        } else if (type == Long.TYPE) {
            return Long;
        } else if (type == Short.TYPE) {
            return Short;
        } else if (type == Byte.TYPE) {
            return Byte;
        } else if (type == Character.TYPE) {
            return Character;
        } else if (type == Void.TYPE) {
            return Void;
        } else if (type == Boolean.TYPE) {
            return Boolean;
        } else {
            return type;
        }
    }

    public static function convertArgument(runtime:Ruby, argument:Object, parameterType:Class):Object{
        if (argument == null && parameterType.isPrimitive()) {
            throw runtime.newTypeError("primitives do not accept null");
        }
        
        if (argument instanceof JavaObject) {
            argument = ((JavaObject) argument).getValue();
            if (argument == null) {
                return null;
            }
        }
        var type: Class =  primitiveToWrapper(parameterType);
        if (type == Void) {
            return null;
        }
        if (argument instanceof Number) {
            var number: Number =  (Number) argument;
            if (type == Long) {
                return new Long(number.longValue());
            } else if (type == Integer) {
                return new Integer(number.intValue());
            } else if (type == Short) {
                return new Short(number.shortValue());
            } else if (type == Byte) {
                return new Byte(number.byteValue());
            } else if (type == Character) {
                return new Character((char) number.intValue());
            } else if (type == Double) {
                return new Double(number.doubleValue());
            } else if (type == Float) {
                return new Float(number.floatValue());
            }
        }
        if (isDuckTypeConvertable(argument.getClass(), parameterType)) {
            var rubyObject: RubyObject =  (RubyObject) argument;
            if (!rubyObject.respondsTo("java_object")) {
                var javaUtilities: IRubyObject =  runtime.getJavaSupport().getJavaUtilitiesModule();
                var javaInterfaceModule: IRubyObject =  Java.get_interface_module(javaUtilities, JavaClass.get(runtime, parameterType));
                if (!((RubyModule)javaInterfaceModule).isInstance(rubyObject)) {
                    rubyObject.extend(new IRubyObject[] {javaInterfaceModule});
                }
                var context: ThreadContext =  runtime.getCurrentContext();
                if (rubyObject instanceof RubyProc) {
                    // Proc implementing an interface, pull in the catch-all code that lets the proc get invoked
                    // no matter what method is called on the interface
                    rubyObject.instance_eval(context, new IRubyObject[] {
                        runtime.newString("extend Proc::CatchAll")}, Block.NULL_BLOCK);
                }
                var jo: JavaObject =  (JavaObject) rubyObject.instance_eval(context, new IRubyObject[] {
                    runtime.newString("send :__jcreate_meta!")}, Block.NULL_BLOCK);
                return jo.getValue();
            }
        }
        return argument;
    }
    
    public static function isDuckTypeConvertable(providedArgumentType:Class, parameterType:Class):Boolean{
        return parameterType.isInterface() && !parameterType.isAssignableFrom(providedArgumentType) 
            && RubyObject.isAssignableFrom(providedArgumentType);
    }
*/
}
}